#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "stuffer.h"
#include "linkedlist.h"

enum operations_t {
  OP_NONE = 0,
  OP_BUILD,
  OP_LIST,
  OP_EXTRACT
};


enum operations_t operation;
char *archive,*file_list;

void initialize_variables();
void parse_args(int argc,char **argv);
void die(char *msg);
void help();
void build_archive();
void list_archive();
void extract_archive();
void truncate_string(char *dst,const char *src,size_t n);
void generate_permissions(int *perm,const char *str);
void extract_file(FILE *arc,struct stuffer_entry_t *sfs_entry);

int main(int argc,char **argv)
{
  
  initialize_variables();

  parse_args(argc,argv);


  switch (operation)
    {
    case OP_BUILD:
      build_archive();
      break;

    case OP_LIST:
      list_archive();
      break;

    case OP_EXTRACT:
      extract_archive();
      break;

    default:
      die("Unspecified operation.\n");
    }
  

  return 0;
}

void initialize_variables()
{
  operation=OP_NONE;
  archive=NULL;
  file_list=NULL;
}

void parse_args(int argc,char **argv)
{
  char flag;

  if (argc<2 || strlen(argv[1])<2)
    die("You must specify at least one argument.\nUse -h for help.\n");
  
  switch(argv[1][1])
    {
    case 'h':
      help();
      break;

    case 'b':
      operation=OP_BUILD;
      break;

    case 'l':
      operation=OP_LIST;
      break;

    case 'e':
      operation=OP_EXTRACT;
      break;

    default:
      die("The argument is not valid.\nUse -h for help.\n");
    }

  if (operation != OP_NONE)
    {
      if (argc<3)
	die("You must specify the archive.\n");

      archive=(char *)malloc(sizeof(char)*strlen(argv[2]));
      strcpy(archive,argv[2]);
      
      if (operation == OP_BUILD)	
	{
	  if (argc<4)
	    die("You must specify the file list.\n");

	  file_list=(char *)malloc(sizeof(char)*strlen(argv[3]));
	  strcpy(file_list,argv[3]);
	}
    }
}

void die(char *msg)
{
  fprintf(stderr,msg);
  exit(1);
}

void help()
{
  printf("Stuffer 0.1\n");
  printf("stuffer -[blh] {archive} [list]\n");
  printf("     -b      build   {archive} {list}\n");
  printf("     -l      list    {archive}\n");
  printf("     -e      extract {archive}\n");
  printf("     -h      help\n");

  die("");
}

void truncate_string(char *dst,const char *src,size_t n)
{
  strncpy(dst,src,n);
  dst[n-1]='\0';

  if (strlen(src)>n)
    printf("Warning: file %s exceeds maximum length (%d). Truncating.\n",src,n);
}

void generate_permissions(int *perm,const char *str)
{
  *perm=0;
}

void build_archive()
{
  FILE *arc,*lst,*fle;
  char buf_name[128];
  char buf_realname[128],*real_name;
  char buf_perm[128];
  struct stuffer_header_t *sfs_header;
  struct stuffer_entry_t *sfs_entry;
  ptr_list_t list;
  ptr_iterator_t iter;
  ptr_list_t list_names;
  int i;
  char file_name[FILE_NAME_SIZE];
  int file_size,file_flags,file_index;
  long cumulated_offset;
  long cumulated_size;
  
  file_index=0;
  cumulated_offset=0;
  cumulated_size=0;

  list=llist_make_list();
  list_names=llist_make_list();

  sfs_header=(struct stuffer_header_t *)malloc(sizeof(struct stuffer_header_t));

  sfs_header->magic=STUFFER_MAGIC;
  sfs_header->version=STUFFER_VERSION;
  sfs_header->num_files=0;
  sfs_header->total_size=sizeof(struct stuffer_header_t);

  lst=fopen(file_list,"r");
  
  printf("Building archive %s with file list: %s\n",archive,file_list);

  // Read each file from the list
  while (!feof(lst))
    {
      fscanf(lst,"%s\t%s\t%s\n",buf_perm,buf_realname,buf_name);
      //printf("%s\t%s\n",buf_perm,buf_name);
      truncate_string(file_name,buf_name,FILE_NAME_SIZE);
      generate_permissions(&file_flags,buf_perm);

      // get size of file
      fle=fopen(buf_realname,"r");
      fseek(fle,0,SEEK_END);
      file_size=(int)ftell(fle);
      fclose(fle);

      cumulated_size += file_size;

      // create entry for this file
      sfs_entry=(struct stuffer_entry_t *)malloc(sizeof(struct stuffer_entry_t));
      
      sfs_entry->index=file_index++;
      sfs_entry->offset=cumulated_offset;
      sfs_entry->size=file_size;
      sfs_entry->flags=file_flags;
      strcpy(sfs_entry->name,file_name);
      
      // Add it to the list
      llist_add_last(list,sfs_entry);

      // Add the real file name to the other list
      real_name=(char *)malloc(strlen(buf_realname)*sizeof(char));
      strcpy(real_name,buf_realname);
      llist_add_last(list_names,real_name);

      cumulated_offset += file_size;
    }

  fclose(lst);

  // Adjust values
  sfs_header->num_files=file_index;
  // still partial (need to add the file sizes)
  sfs_header->total_size += file_index * sizeof(struct stuffer_entry_t);

  // Adjust offsets
  iter=llist_get_nodes(list);
  while (llist_iter_has_next(iter))
    {
      sfs_entry=llist_iter_next(iter);
      // still partial
      sfs_entry->offset += sfs_header->total_size;
    }
  free(iter);
  
  // Adjust total size
  sfs_header->total_size += cumulated_size;

  // Start generating archive
  arc=fopen(archive,"w");

  // write header
  fwrite(sfs_header,sizeof(struct stuffer_header_t),1,arc);

  // write entries
  iter=llist_get_nodes(list);
  while (llist_iter_has_next(iter))
    {
      sfs_entry=llist_iter_next(iter);
      fwrite(sfs_entry,sizeof(struct stuffer_entry_t),1,arc);
      printf("Writing:  %s\n",sfs_entry->name);
    }
  free(iter);

  // write file data
  iter=llist_get_nodes(list_names);
  while (llist_iter_has_next(iter))
    {
      real_name=llist_iter_next(iter);

      fle=fopen(real_name,"r");

      // copy file
      while (!feof(fle))
	{
	  int ch;

	  ch=fgetc(fle);
	  
	  if (!feof(fle))
	    fputc(ch,arc);
	}

      fclose(fle);
    }
  free(iter);

  fclose(arc);

  free(sfs_header);

  printf("\nWritten %d files.\n",file_index);

  // note: list and information is not freed... but no prob, the program ends now
}

void list_archive()
{
  FILE *arc,*fle;
  struct stuffer_header_t *sfs_header;
  struct stuffer_entry_t *sfs_entry;
  ptr_list_t list;
  ptr_iterator_t iter;
  int i;
  int file_index;

  file_index=0;

  sfs_header=(struct stuffer_header_t *)malloc(sizeof(struct stuffer_header_t));
  sfs_entry=(struct stuffer_entry_t *)malloc(sizeof(struct stuffer_entry_t));

  printf("Reading archive %s\n",archive);

  arc=fopen(archive,"r");

  // read header
  fread(sfs_header,sizeof(struct stuffer_header_t),1,arc);
  file_index=sfs_header->num_files;

  for (i=0;i<file_index;i++)
    {
      // read entry
      fread(sfs_entry,sizeof(struct stuffer_entry_t),1,arc);
      //printf("@ %d\n",sfs_entry->offset);
      printf("%d:\t----\t%d\t%s\n",sfs_entry->index,sfs_entry->size,sfs_entry->name);
    }

  fclose(arc);

  printf("\nRead %d files.\n",file_index);
}


void extract_archive()
{
  FILE *arc,*fle;
  struct stuffer_header_t *sfs_header;
  struct stuffer_entry_t *sfs_entry;
  ptr_list_t list;
  ptr_iterator_t iter;
  int i;
  int file_index;

  file_index=0;

  sfs_header=(struct stuffer_header_t *)malloc(sizeof(struct stuffer_header_t));
  sfs_entry=(struct stuffer_entry_t *)malloc(sizeof(struct stuffer_entry_t));

  printf("Extracting archive %s\n",archive);

  arc=fopen(archive,"r");

  // read header
  fread(sfs_header,sizeof(struct stuffer_header_t),1,arc);
  file_index=sfs_header->num_files;

  for (i=0;i<file_index;i++)
    {
      // read entry
      fread(sfs_entry,sizeof(struct stuffer_entry_t),1,arc);
      //printf("@ %d\n",sfs_entry->offset);
      //printf("%d:\t----\t%d\t%s\n",sfs_entry->index,sfs_entry->size,sfs_entry->name);
      printf("Extracting: %s\n",sfs_entry->name);
      extract_file(arc,sfs_entry);
    }

  fclose(arc);

  printf("\nExtracted %d files.\n",file_index);
}

void extract_file(FILE *arc,struct stuffer_entry_t *sfs_entry)
{
  FILE *dst;
  long pos;
  int i;

  // Save current file pointer
  pos=ftell(arc);

  // Seek to file position
  fseek(arc,sfs_entry->offset,SEEK_SET);

  // Open destination file
  dst=fopen(sfs_entry->name,"w");

  // copy file
  for (i=0;i<sfs_entry->size;i++)
    fputc(fgetc(arc),dst);
  
  fclose(dst);

  // Restore file pointer
  fseek(arc,pos,SEEK_SET);
}
